
package system.train;

import java.util.logging.Level;
import java.util.logging.Logger;
import system.communication.Monitor;
import system.communication.HandController;
import system.track.Block;
import system.track.Direction;
import system.track.SwitchAngle;

/**
 * This class inherits the Train functionality and is meant to virtually
 * represent a Train controlled by the HandController.
 *
 * @author elliot
 */
public class HandControlTrain extends Train
{

    private boolean sensorTrip;
    private boolean statusUpdate;
    private boolean running;

    private HandController hc;

    /**
     * Overloaded constructor for the HandControlTrain.
     * @param block Block object to set the starting Block for the
     * HandControlTrain
     * @param id int ID to register to the train
     * @param hcID the id of the controlling HandController.
     */
    public HandControlTrain(Block block, int id, int hcID)
    {
        super(block, id);
        hc = Monitor.getInstance().getMessageListener().getHC(hcID);
        hc.registerTrain(this);
        sensorTrip = false;
        running = false;
        setDirection(hc.getCurrentDirection());
    }

    @Override
    public void setRoute(Direction direction)
    {

    }

    @Override
    public void run()
    {
        running = true;
        initialize();
        while (running)
        {
            waitForNotification();
            if (sensorTrip)
            {
                threadTripSensor();
            }
            if (statusUpdate)
            {
                threadStatusUpdate();
            }
        }
    }

    @Override
    public void setDirection(Direction direction)
    {
        super.setDirection(direction);
        Monitor.getInstance().setBlock(this.getCurrentBlock().getID(), direction, this.getSpeed());
    }

    /**
     * threadStatusUpdate is internal to the HandControlTrain and is run with
     * updates to the controlling HandController.
     */
    private void threadStatusUpdate()
    {
        statusUpdate = false;
        //Change in status
            if (hc.hasHaltedChanged())
            {
                if (!hc.getCurrentHaltedStatus())
                {
                    pause();
                }
                else
                {
                    unpause();
                }
            }
            if(hc.hasDirectionChanged())
            { // direction has changed
                changeDirection();
            } 
            else if (hc.hasSwitchAngleChanged())
            {
                changeSwitch();
            }
    }

    /**
     * sets speed of the train's current block and the block immediate to the
     * current block as well as unreserves the block immediate to the current
     * block and unregisters sensors.
     */
    @Override
    public void pause()
    {
        getCurrentBlock().setDirection(getDirection(), 0);
        getCurrentBlock()
                .getSpecificBlock(getDirection(), hc.getCurrentSwitchAngle())
                .setDirection(getDirection(), 0);
        getCurrentBlock()
                .getSpecificBlock(getDirection(), hc.getCurrentSwitchAngle())
                .unreserve();
        RouteActions.UNREGISTER_SENSORS.executeAction(this);
    }

    /**
     * unpause will attempt to reserve the block immediate to the current block
     * in the current direction and switch angle corresponding to the
     * HandController. If successful it will set speed to 4 and direction to the
     * current direction of the train to the current block and block immediate
     * to the current block, otherwise a call to crash() is made.
     */
    public void unpause()
    {
        boolean reserveAttempt = getCurrentBlock()
                .getSpecificBlock(getDirection(), hc.getCurrentSwitchAngle())
                .reserve();
        if (canSetNextSwitch())
        {
            getCurrentBlock().getSwitch(getDirection())
                    .setAngle(hc.getCurrentSwitchAngle());
        } else {
            getCurrentBlock().autoSetSwitch(getDirection());
        }
        if (reserveAttempt)
        {
            RouteActions.REGISTER_SENSORS.executeAction(this);
            activateNextBlock(getCurrentBlock(),
                    getCurrentBlock().getNextBlock(getDirection()));
            RouteActions.ACTIVATE_CURRENT_BLOCK.executeAction(this);
        }
        else
        {
            pause();
        }
    }

    /**
     * Finds if the next switch to the current block is able to be set.
     *
     * @return boolean value representing if the next switch is able to be set.
     */
    private boolean canSetNextSwitch()
    {
        return this.getCurrentBlock().canSetSwitch(this.getDirection());
    }

    /**
     * Finds if the previous switch to the current block is able to be set.
     *
     * @return boolean value representing if the previous switch is able to be set.
     */
    private boolean canSetPreviousSwitch()
    {
        return this.getCurrentBlock().canSetSwitch(getOppositeDirection());
    }

    /**
     * attempts to set a switch on the current block with the switch angle values
     * of the controlling HandController. If it is able to set the switch it will
     * try to reserve the next block in the specified direction and switch angle
     * of the train and handcontroller respectively. If the attempt fails a call
     * to crash() is made.
     */
    private void changeSwitch()
    {
        if(getCurrentBlock().canSetSwitch(getDirection()))
        {
            getCurrentBlock().getNextBlock(getDirection())
                    .setDirection(getDirection(), 0);
            getCurrentBlock().getNextBlock(getDirection()).unreserve();
            boolean reserveAttempt = getCurrentBlock()
                    .getSpecificBlock(getDirection(), hc.getCurrentSwitchAngle())
                    .reserve();
            if (reserveAttempt)
            {
                getCurrentBlock().getSwitch(getDirection())
                    .setAngle(hc.getCurrentSwitchAngle());
                activateNextBlock(getCurrentBlock(),
                    getCurrentBlock().getNextBlock(getDirection()));
            }
            else
            {   //We can't reserve the block, so we'll just ignore the setting.
                
            }
        }
        
    }

    /**
     * changeDirection attempts to use the change in direction from the
     * controlling HandController and make a change in Direction in the train.
     */
    private synchronized void changeDirection() {
        //Unreserve the next block.
        this.getCurrentBlock().getNextBlock(this.getDirection()).unreserve();

        //Turn off the next block.
        this.getCurrentBlock().getNextBlock(getDirection())
                .setDirection(getDirection(), 0);

        //Unregister for sensors and toggle our direciton.
        RouteActions.UNREGISTER_SENSORS.executeAction(this);
        RouteActions.TOGGLE_DIRECTION.executeAction(this);

        //Reserve the reverse block. getSpecificBlock ignores any
        //switch angle argument if it doesn't matter.
        boolean reserveAttempt = this.getCurrentBlock().getSpecificBlock(
                this.getDirection(), hc.getCurrentSwitchAngle()).reserve();
        if (reserveAttempt)
        { //successfully reserved reverse direction block.
            RouteActions.REGISTER_SENSORS.executeAction(this);
            if (canSetNextSwitch())
            { //we can set the next switch, so we need to set the angle.
                switch (hc.getCurrentSwitchAngle())
                {
                    case NINETY_DEGREES:
                        RouteActions.SET_NEXT_SWITCH_90.executeAction(this);
                        break;
                    case ONE_EIGHTY_DEGREES:
                        RouteActions.SET_NEXT_SWITCH_180.executeAction(this);
                        break;
                }
            } //end can set next switch
            else
            { //we can't set the next switch, autoset it.
                getCurrentBlock().autoSetSwitch(getDirection());
            }
            RouteActions.ACTIVATE_CURRENT_BLOCK.executeAction(this);
            activateNextBlock(getCurrentBlock(),
                    getCurrentBlock().getNextBlock(getDirection()));
        } //end reserve attempt
        else
        { // we can't reserve the block so we simply flip back to the
          // original direction.
            changeDirection();
        }
    }

    /**
     * crash is used to stop the train (essentially setting the speed of the
     * current block and block immediate to the current block to zero). An LED
     * is set to all lights on to indicate to the HandController User that a
     * crash has occurred. The user may attempt to flip the Halt switch from
     * Inactive to Active to Inactive to try and uncrash.
     */
    private void crash()
    { //The user is trying to run into the other train!  Oh noes!
        getCurrentBlock().setDirection(getDirection(), 0);
        Monitor.getInstance().setLED(255);
        waitForSwitchFlip();
        uncrash();
    }

    /**
     * attempts to reset the train from a crashed status. Indication to the user
     * is sent by every other light on the LED turning on. If the uncrash attempt
     * fails the train is sent back into a crashed status.
     */
    private void uncrash()
    {
        Monitor.getInstance().setLED(170);
        //setDirection(hc.getCurrentDirection());
        if (hc.hasDirectionChanged())
        {
            RouteActions.TOGGLE_DIRECTION.executeAction(this);
        }
        boolean reserveAttempt = this.getCurrentBlock().getSpecificBlock(
                this.getDirection(), hc.getCurrentSwitchAngle()).reserve();
        if (reserveAttempt)
        {
            Monitor.getInstance().setLED(0);
            RouteActions.REGISTER_SENSORS.executeAction(this);
            if (canSetNextSwitch())
            { //we can set the next switch, so we need to set the angle.
                switch (hc.getCurrentSwitchAngle())
                {
                    case NINETY_DEGREES:
                        RouteActions.SET_NEXT_SWITCH_90.executeAction(this);
                        break;
                    case ONE_EIGHTY_DEGREES:
                        RouteActions.SET_NEXT_SWITCH_180.executeAction(this);
                        break;
                }
            } //end can set next switch
            else
            { //we can't set the next switch, autoset it.
                getCurrentBlock().autoSetSwitch(getDirection());
            }
            RouteActions.ACTIVATE_CURRENT_BLOCK.executeAction(this);
            activateNextBlock(getCurrentBlock(),
                    getCurrentBlock().getNextBlock(getDirection()));
            //getCurrentBlock().setDirection(hc.getCurrentDirection(), getSpeed());
            /*switch (hc.getCurrentSwitchAngle())
            {
                case NINETY_DEGREES:
                    RouteActions.SET_NEXT_SWITCH_90.executeAction(this);
                    break;
                case ONE_EIGHTY_DEGREES:
                    RouteActions.SET_NEXT_SWITCH_180.executeAction(this);
                    break;
            }
            RouteActions.REGISTER_SENSORS.executeAction(this);
            activateNextBlock(getCurrentBlock(),
                    getCurrentBlock().getNextBlock(getDirection()));
            RouteActions.ACTIVATE_CURRENT_BLOCK.executeAction(this);*/
        }
        else
        {
            crash();
        }

    }

    /**
     * Attempts a reservation of a specified block with a direction and switch
     * angle.
     *
     * @param d Direction
     * @param a SwitchAngle
     * @return boolean representing if the reserve was successful.
     */
    private boolean reserveBlock(Direction d, SwitchAngle a)
    {
        return this.getCurrentBlock().getSpecificBlock(d,a).reserve();
    }

    /**
     * gets the opposite direction of the current direction of the train.
     *
     * @return Direction enum
     */
    private Direction getOppositeDirection()
    {
        Direction oppositeDirection = Direction.CLOCKWISE;
        if(getDirection() == oppositeDirection)
        {
            oppositeDirection = Direction.COUNTERCLOCKWISE;
        }
        return oppositeDirection;
    }

    /**
     * Initializes the HandControlTrain by setting it's current beginning status
     * to the first messages received from the HandController.
     */
    private synchronized void initialize()
    {
       getCurrentBlock().reserve();
       if (canSetNextSwitch())
       {
           switch (hc.getCurrentSwitchAngle())
           {
               case NINETY_DEGREES:
                   RouteActions.SET_NEXT_SWITCH_90.executeAction(this);
                   break;
               case ONE_EIGHTY_DEGREES:
                   RouteActions.SET_NEXT_SWITCH_180.executeAction(this);
                   break;
           }
       }
       if (hc.getCurrentHaltedStatus())
       {
           this.getCurrentBlock().getNextBlock(this.getDirection()).reserve();
           activateNextBlock(getCurrentBlock(),
                    getCurrentBlock().getNextBlock(getDirection()));
           RouteActions.REGISTER_SENSORS.executeAction(this);
           RouteActions.ACTIVATE_CURRENT_BLOCK.executeAction(this);
       }
    }

    @Override
    public boolean isHandControlled()
    {
        return true;
    }
    
    private synchronized void threadTripSensor()
    {
        try
        {
            Thread.sleep(2000);
        }
        catch (InterruptedException e)
        {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
        sensorTrip = false;
        RouteActions.UNREGISTER_SENSORS.executeAction(this);
        RouteActions.DEACTIVATE_PREVIOUS_BLOCK.executeAction(this);
        this.getCurrentBlock().unreserve();
        moveAheadABlock(getCurrentBlock(),
                getCurrentBlock().getNextBlock(getDirection()));
        boolean reserveAttempt = this.getCurrentBlock()
                .getSpecificBlock(getDirection(), hc.getCurrentSwitchAngle())
                .reserve();
        if (reserveAttempt)
        {
            if (canSetNextSwitch())
            {
                switch (hc.getCurrentSwitchAngle())
                {
                    case NINETY_DEGREES:
                        RouteActions.SET_NEXT_SWITCH_90.executeAction(this);
                        break;
                    case ONE_EIGHTY_DEGREES:
                        RouteActions.SET_NEXT_SWITCH_180.executeAction(this);
                        break;
                }
            }
            else
            {
                getCurrentBlock().autoSetSwitch(getDirection());
            }
            RouteActions.REGISTER_SENSORS.executeAction(this);
            activateNextBlock(getCurrentBlock(),
                    getCurrentBlock().getNextBlock(getDirection()));
        }
        else
        { //we couldn't reserve the block.
            crash();
        }
    }

    private void waitForSwitchFlip()
    {
        hc.initCrashFlip();
        while(!hc.haltSwitchFlipped())
        {
            try
            {
                Thread.sleep(25);
            }
            catch(InterruptedException e)
            {
                System.out.println("HC Train: " + e.getMessage());
                e.printStackTrace();
            }
        }
    }

    public synchronized void waitForNotification()
    {
        try
        {
            wait();
        }
        catch (InterruptedException ex)
        {
            Logger.getLogger(HandControlTrain.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    @Override
    public synchronized void tripSensor()
    {
        sensorTrip = true;
        notify();
    }

    public synchronized void statusUpdate()
    {
        statusUpdate = true;
        notify();
    }

    private void activateNextBlock(Block currentBlock, Block nextBlock)
    {
        if ( (currentBlock.getID() == 4 &&  //If the current block is 4
              nextBlock.getID() == 1)       //and the next block is 1
             ||                             //OR
             (currentBlock.getID() == 1 &&  //If the current block is 1
              nextBlock.getID() == 4)       //and the next block is 4
           )
        { //Crossing polarity boundary
            System.out.println("Polarity Boundary, Activate next block");
            nextBlock.setDirection(getOppositeDirection(), getSpeed());
        }
        else
        { //Normal activate next block.
            RouteActions.ACTIVATE_NEXT_BLOCK.executeAction(this);
        }
    }

    private void moveAheadABlock(Block currentBlock, Block nextBlock)
    {
        //Move train ahead.
        RouteActions.MOVE_TRAIN_AHEAD.executeAction(this);
        if ( (currentBlock.getID() == 4 &&  //If the current block is 4
              nextBlock.getID() == 1)       //and the next block is 1
             ||                             //OR
             (currentBlock.getID() == 1 &&  //If the current block is 1
              nextBlock.getID() == 4)       //and the next block is 4
           )
        { //Crossing polarity boundary
            System.out.println("Polarity Boundary, move next block.");
            System.out.println("New current block ID: "
                    + getCurrentBlock().getID());
            RouteActions.TOGGLE_DIRECTION.executeAction(this);
        }
    }

}
